# 作者：徐宇杰
# -*- coding = utf-8 -*-
# @Time :2024/7/24 下午1:53
# @File:backtestingversion3.py
# @Software:PyCharm
import json
import time
import math
import sys
import warnings
from datetime import date, timedelta

import numpy as np
import pandas as pd
import requests
from tqdm import tqdm

pd.set_option('float_format', lambda x: '%.2f' % x)

warnings.simplefilter(action='ignore', category=FutureWarning)

# 数据传输的初步处理
# Token accessToken 及权限校验机制
getAccessTokenUrl = 'https://quantapi.51ifind.com/api/v1/get_access_token'
# 获取refresh_token需下载Windows版本接口包解压，打开超级命令-工具-refresh_token查询
refreshtoken = (
    'eyJzaWduX3RpbWUiOiIyMDI0LTA3LTEwIDEwOjQzOjU3In0=.eyJ1aWQiOiI3MDQyMzg2NjciLCJ1c2VyIjp7ImFjY291bnQiOiJkZHFoMTIwIiwiYXV0aFVzZXJJbmZvIjp7IkVleGNlbFBheWVycyI6IjE3NDU4OTE5NjkwMDAifSwiY29kZUNTSSI6W10sImNvZGVaekF1dGgiOltdLCJoYXNBSVByZWRpY3QiOmZhbHNlLCJoYXNBSVRhbGsiOmZhbHNlLCJoYXNDSUNDIjpmYWxzZSwiaGFzQ1NJIjpmYWxzZSwiaGFzRXZlbnREcml2ZSI6ZmFsc2UsImhhc0ZUU0UiOmZhbHNlLCJoYXNGYXN0IjpmYWxzZSwiaGFzRnVuZFZhbHVhdGlvbiI6ZmFsc2UsImhhc0hLIjp0cnVlLCJoYXNMTUUiOmZhbHNlLCJoYXNMZXZlbDIiOmZhbHNlLCJoYXNSZWFsQ01FIjpmYWxzZSwiaGFzVVMiOmZhbHNlLCJoYXNVU0FJbmRleCI6ZmFsc2UsIm1hcmtldENvZGUiOiIxNjszMjsxNDQ7MTc2OzExMjs4ODs0ODsxMjg7MTY4LTE7MTg0OzIwMDsyMTY7MTA0OzEyMDsxMzY7MjMyOzU2Ozk2OzE2MDs2NDsiLCJtYXhPbkxpbmUiOjEsIm5vRGlzayI6ZmFsc2UsInByb2R1Y3RUeXBlIjoiU1VQRVJDT01NQU5EUFJPRFVDVCIsInJlZnJlc2hUb2tlbkV4cGlyZWRUaW1lIjoiMjAyNS0wNC0yOSAwOTo1OToyOSIsInNlc3NzaW9uIjoiOTVkZDA0MDM3Y2M0OGFiMTE5NWRiODdmMjI2YmM3YzciLCJzaWRJbmZvIjp7fSwidWlkIjoiNzA0MjM4NjY3IiwidXNlclR5cGUiOiJGUkVFSUFMIiwid2lmaW5kTGltaXRNYXAiOnt9fX0=.7D5E59D38C226AF1E8E3B35B7F905FFF492B62F4FED33A2879D899ABA815B7CA')
getAccessTokenHeader = {"Content- Type": "application/json", "refresh_token": refreshtoken}
getAccessTokenResponse = requests.post(url=getAccessTokenUrl, headers=getAccessTokenHeader)
accessToken = json.loads(getAccessTokenResponse.content)['data']['access_token']
thsHeaders = {"Content-Type": "application/json", "access_token": accessToken}


# 输入的参数
# 1.品种（现在只支持上海期货交易所的那只主连期货），例如 "RB"（螺纹钢）、"RU"（橡胶）等（字符串类型）
# 这个版本是输入code,为品种对应的主连代码，如RBZL.SHF（螺纹钢），（黄金、白银没有测试实现）
# 2.开始日期与结束日期(start_date,end_date)(需要字符串类型) 如"2023-08-14 10:00:00"   "2024-07-01 10:00:00"，第一天不能在到期日期内
# 3.数据的频率(frequency) 如1、30等（表示一分钟、三十分钟） 整数类型，默认为30
# 4.期权选择的方法(option_selection) 不存在到期日，对于到期日前五天的数据直接用下一个指标的期权代替
# 默认为最高最低四分之一百位取整数的方法,方法0为默认方法，range_window  整数类型  参数 为取过去多长间隔的数据来采取回推
# 5.止损和止盈的标准(profit_stop,loss_stop) 默认为0.1 和 -0.1 （到期日不计算也为默认,已经在里面实现了）
# 6.分批(quant)功能的实现，如果分批，输入分批的数目（最大值），默认值为1，也就是不分批
def initialization(thsResponse):
    data0 = json.loads(thsResponse.content)
    data1 = pd.json_normalize(data0['tables'])
    un_nested_lst = []
    for col in data1.columns:
        un_nested_lst.append(data1[col].apply(pd.Series).stack())
    data2 = pd.concat(un_nested_lst, axis=1, keys=data1.columns)
    data2 = data2.reset_index(drop=True)
    return data2


def Import_Futures_Data(code, start_date, end_date, frequency):
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": code,
               "indicators":
                   "open,high,low,close",
               "starttime": start_date,
               "endtime": end_date,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data = json.loads(thsResponse.content)
    df = pd.json_normalize(data['tables'])
    try:
        df2 = df.set_index(['thscode'])
    except Exception as e:
        print(data['errmsg'])
        print(thsPara)
        sys.exit()
    un_nested_list = []
    for col in df2.columns:
        un_nested_list.append(df2[col].apply(pd.Series).stack())
    data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
    # 设置二级索引
    data = data.reset_index()
    data = data.set_index(['thscode', 'time'])
    # 格式化,行转列
    data = data.drop(columns=['level_1'])
    data = data.reset_index()
    # 设置列索引名称
    data.columns = ['thscode', 'time', 'close', 'open', 'high', 'low']
    # 去除NaN值
    data.dropna(inplace=True)
    data = data.reset_index()
    data = data.iloc[:, 1:]
    return data


def Import_Local_Data(code, local_path):
    print("导入本地数据阶段")
    if local_path is None:
        print("请输入正确的路径")
        sys.exit()
    data0 = pd.read_excel(local_path, dtype={'Key_Month': str})
    mask = data0['Thscode'] == code
    price_change = data0.loc[mask, 'Tick Size'].iloc[0]
    key_months = data0.loc[mask, 'Key_Month'].iloc[0]
    contract_multiplier = data0.loc[mask, 'Contract_Multiplier'].iloc[0]
    return price_change, key_months, contract_multiplier


def calculate_option_some_parameters(df, Futures_code, start_date, end_date, count, price_change, frequency,
                                     range_window, i):
    # 导入对应期货的数据
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": Futures_code,
               "indicators": "open,high,low,close",
               "starttime": start_date,
               "endtime": end_date,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data = json.loads(thsResponse.content)
    df1 = pd.json_normalize(data['tables'])
    try:
        df2 = df1.set_index(['thscode'])
    except Exception as e:
        print(data['errmsg'])
        print(thsPara)
        sys.exit()
    un_nested_list = []
    for col in df2.columns:
        un_nested_list.append(df2[col].apply(pd.Series).stack())
    data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
    # 设置二级索引
    data = data.reset_index()
    data = data.set_index(['thscode', 'time'])
    # 格式化,行转列
    data = data.drop(columns=['level_1'])
    data = data.reset_index()
    # 设置列索引名称
    data.columns = ['thscode', 'time', 'close', 'open', 'high', 'low']
    # 去除NaN值
    data.dropna(inplace=True)
    data = data.reset_index()
    data = data.iloc[:, 1:]
    for z in range(0, count):
        data_temp = data.iloc[-range_window - count + z:-count + z, ]
        data_temp = data_temp.reset_index(drop=True)
        # 计算上下界限
        upper = data_temp['high'].max()
        lower = data_temp['low'].min()
        # 使得期权选择能够选择所有
        # 计算期权名称的执行价，有时候是多少以下一个变化，多少以上另一个变化，可以读取进来两个，根据值来判断，还需要设置一个（没有实现）
        UpDown = upper - lower
        otm_range = int(UpDown / 4)
        price1 = round(data_temp.iloc[-1, 2] + otm_range)
        strike_price1 = int(price1 / price_change) * price_change
        while abs(strike_price1 - price1) > (price_change / 2):
            strike_price1 = strike_price1 + price_change
        K_call = int(strike_price1)
        df.iloc[i - count + z + 1, 6] = K_call
        price2 = round(data_temp.iloc[-1, 2] - otm_range)
        strike_price2 = int(price2 / price_change) * price_change
        while abs(strike_price2 - price2) > (price_change / 2):
            strike_price2 = strike_price2 + price_change
        K_put = int(strike_price2)
        df.iloc[i - count + z + 1, 7] = K_put
        if Futures_code[-3:] == "DCE":
            str_list = list(Futures_code)
            str_list.insert(-4, "-C-{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(Futures_code)
            str_list.insert(-4, "-P-{}".format(K_put))
            code_put = ''.join(str_list)
        else:
            str_list = list(Futures_code)
            str_list.insert(-4, "C{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(Futures_code)
            str_list.insert(-4, "P{}".format(K_put))
            code_put = ''.join(str_list)
        df.iloc[i - count + z + 1, 0] = code_call
        df.iloc[i - count + z + 1, 1] = code_put
        df.iloc[i - count + z + 1, 3] = data_temp.iloc[-1, 2]
        df.iloc[i - count + z + 1, 4] = upper
        df.iloc[i - count + z + 1, 5] = lower
    return df


# 上下两个方法可以合并为一个（没有优化）
def change_Futures_data(df, t_Futures_code, start_date, date0, frequency, price_change, i, range_window, count):
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": t_Futures_code,
               "indicators": "open,high,low,close",
               "starttime": start_date,
               "endtime": date0,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data = json.loads(thsResponse.content)
    df1 = pd.json_normalize(data['tables'])
    try:
        df2 = df1.set_index(['thscode'])
    except Exception as e:
        print(data['errmsg'], thsPara, e)
        sys.exit()
    un_nested_list = []
    for col in df2.columns:
        un_nested_list.append(df2[col].apply(pd.Series).stack())
    data = pd.concat(un_nested_list, axis=1, keys=df2.columns)
    # 设置二级索引
    data = data.reset_index()
    data = data.set_index(['thscode', 'time'])
    # 格式化,行转列
    data = data.drop(columns=['level_1'])
    data = data.reset_index()
    # 设置列索引名称
    data.columns = ['thscode', 'time', 'close', 'open', 'high', 'low']
    # 去除NaN值
    data.dropna(inplace=True)
    data = data.reset_index()
    data = data.iloc[:, 1:]
    for w in range(0, count):
        data_temp = data.iloc[-range_window + w - count:w - count, ]
        data_temp = data_temp.reset_index(drop=True)
        # 计算上下界限
        upper = data_temp['high'].max()
        lower = data_temp['low'].min()
        # 计算期权名称的执行价，有时候是多少以下一个变化，多少以上另一个变化，可以读取进来两个，根据值来判断，还需要设置一个（没有实现）
        UpDown = upper - lower
        otm_range = int(UpDown / 4)
        price1 = round(data_temp.iloc[-1, 2] + otm_range)
        strike_price1 = int(price1 / price_change) * price_change
        while abs(strike_price1 - price1) > (price_change / 2):
            strike_price1 = strike_price1 + price_change
        K_call = int(strike_price1)
        price2 = round(data_temp.iloc[-1, 2] - otm_range)
        strike_price2 = int(price2 / price_change) * price_change
        while abs(strike_price2 - price2) > (price_change / 2):
            strike_price2 = strike_price2 + price_change
        K_put = int(strike_price2)
        df.iloc[i - count + 1 + w, 6] = K_call
        df.iloc[i - count + 1 + w, 7] = K_put
        df.iloc[i - count + 1 + w, 3] = data_temp.iloc[-1, 2]
        df.iloc[i - count + 1 + w, 4] = upper
        df.iloc[i - count + 1 + w, 5] = lower
        if t_Futures_code[-3:] == "DCE":
            str_list = list(t_Futures_code)
            str_list.insert(-4, "-C-{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(t_Futures_code)
            str_list.insert(-4, "-P-{}".format(K_put))
            code_put = ''.join(str_list)
        else:
            str_list = list(t_Futures_code)
            str_list.insert(-4, "C{}".format(K_call))
            code_call = ''.join(str_list)
            str_list = list(t_Futures_code)
            str_list.insert(-4, "P{}".format(K_put))
            code_put = ''.join(str_list)
        df.iloc[i - count + 1 + w, 0] = code_call
        df.iloc[i - count + 1 + w, 1] = code_put
    return df


def Identifying_Options(data, code, price_change, key_months, range_window, frequency):
    # 初步筛选阶段
    print("初步筛选阶段")
    df = pd.DataFrame(columns=['C_code', 'P_code', 'time', 'close', 'upper', 'lower', 'k_call', 'k_put'])
    list0 = key_months.split(',')
    length0 = len(list0)
    if length0 == 3:
        key_month0 = list0[2]
    for i in tqdm(range(0, len(data) - range_window)):
        # 日期的提取与转换
        timestamp_open = data.iloc[i + range_window, 1]
        new_row = {'time': timestamp_open}
        df.loc[i] = new_row
        index1 = str(timestamp_open).find('-')
        year = int(str(timestamp_open)[:index1]) - 2000
        index2 = str(timestamp_open).find('-', index1 + 1)
        month = int(str(timestamp_open)[index1 + 1:index2])
        if length0 == 3:
            if month < 5:
                month = '05'
            elif month < int(key_month0):
                month = key_month0
            else:
                month = '01'
                year = year + 1
        else:
            if month == 12:
                year = year + 1
                month = list0[0]
            else:
                month = list0[month]
        # 依据日期来定位期货
        new_string = code.replace("ZL", "")
        str_list = list(new_string)
        if code[-3:] == "CZC":
            year = year % 10
        str_list.insert(-4, str(year) + month)
        Futures_code = ''.join(str_list)
        if i == 0:
            temp_Futures_code = Futures_code
            start_date = data.iloc[i, 1]
            count = 1
        else:
            if temp_Futures_code != Futures_code:
                df = calculate_option_some_parameters(df, temp_Futures_code, start_date, df.iloc[i - 1, 2], count,
                                                      price_change, frequency, range_window, i - 1)
                start_date = data.iloc[i - range_window - 1, 1]
                count = 1
                temp_Futures_code = Futures_code
                # 最后一个要特殊处理
                if i == len(data) - range_window - 1:
                    df = calculate_option_some_parameters(df, Futures_code, start_date, df.iloc[i, 2], count,
                                                          price_change, frequency, range_window, i)
            else:
                count += 1
                # 最后一个要特殊处理
                if i == len(data) - range_window - 1:
                    df = calculate_option_some_parameters(df, Futures_code, start_date, df.iloc[i, 2], count,
                                                          price_change, frequency, range_window, i)
    # 按照时间筛选阶段
    print("按照选定的时间筛选阶段")
    # 记录到期日的表
    date_frame = pd.DataFrame(columns=['fore_code', 'over_date', 'due_date'])
    date_flag = 0
    day_limit = 5  # 到期日前五天不做
    count = 0
    temp_t_Futures_code = None
    for i in tqdm(range(0, len(df))):
        date_str1 = df.iloc[i, 2][:10]
        date1 = date.fromisoformat(date_str1)
        # 查询期权的到期时间
        if i == 0 or df.iloc[i, 0][:6] != date_frame.iloc[date_flag - 1, 0]:
            thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
            thsPara = {"codes": df.iloc[i, 0],
                       "startdate": date_str1,
                       "enddate": date_str1,
                       "functionpara":
                           {"Fill": "Blank"},
                       "indipara":
                           [
                               {
                                   "indicator":
                                       "ths_last_strike_date_option"
                               }
                           ]
                       }
            thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
            try:
                str_date1 = json.loads(thsResponse.content)
                str_date2 = pd.json_normalize(str_date1['tables'])
                str_date = str_date2.reset_index(drop=True)
                date_str = str_date.iloc[0, 2]
                date_str2 = date_str[0]
            except Exception as e:
                print(e, thsPara, str_date1)
                sys.exit()
            date2 = date.fromisoformat(date_str2)
            date3 = date2 - timedelta(days=day_limit)
            over_date = date3.strftime("%Y-%m-%d")
            due_date = date2.strftime("%Y-%m-%d")
            time_row = {'fore_code': df.iloc[i, 0][:6], 'over_date': over_date, 'due_date': due_date}
            date_frame.loc[date_flag] = time_row
            date_flag = date_flag + 1
        if date1 < date3:
            if count != 0:
                temp_t_Futures_code = None
                # 执行更换函数
                df = change_Futures_data(df, t_Futures_code, start_date, df.iloc[i - 1, 2], frequency, price_change,
                                         i - 1,
                                         range_window, count)
                count = 0
        else:
            index1 = df.iloc[i, 2].find('-')
            year = int(df.iloc[i, 2][:index1]) - 2000
            index2 = df.iloc[i, 2].find('-', index1 + 1)
            month = int(df.iloc[i, 2][index1 + 1:index2])
            if length0 == 3:
                if month < 5:
                    month = '05'
                elif month < int(key_month0):
                    month = key_month0
                else:
                    month = '01'
                    year = year + 1
            else:
                if month == 12:
                    year = year + 1
                    month = list0[0]
                else:
                    month = list0[month]
            # 对于到期前五天的情况要做特殊处理
            if length0 == 3:
                t_year = year
                if month == '05':
                    t_month = key_month0
                elif month == key_month0:
                    t_month = '01'
                    t_year = year + 1
                else:
                    t_month = '05'
            else:
                t_year = year
                if month == "12":
                    t_year = year + 1
                    t_month = list0[0]
                else:
                    t_month = list0[int(month)]
            new_string = code.replace("ZL", "")
            str_list = list(new_string)
            if code[-3:] == "CZC":
                t_year = t_year % 10
            str_list.insert(-4, str(t_year) + t_month)
            t_Futures_code = ''.join(str_list)
            if temp_t_Futures_code is None:
                temp_t_Futures_code = t_Futures_code
                start_date = data.iloc[i - range_window, 1]
                count = 1
            if t_Futures_code == temp_t_Futures_code:
                count += 1
            if i == len(df)-1:
                df = change_Futures_data(df, t_Futures_code, start_date, df.iloc[i, 2], frequency, price_change,
                                         i, range_window, count)
    return df, date_frame


def get_the_margin_price(str_date, code):
    thsUrl = 'https://quantapi.51ifind.com/api/v1/date_sequence'
    thsPara = {"codes": code,
               "startdate": str_date,
               "enddate": str_date,
               "functionpara":
                   {"Fill": "Blank"},
               "indipara":
                   [
                       {
                           "indicator":
                               "ths_trade_deposit_option"
                       }
                   ]
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data2 = initialization(thsResponse)
    deposit = data2.iloc[0, 2]
    return deposit


def get_the_option_data(code, start_date, close_date, frequency, i, df, date_frame, type0):
    length = len(df)
    thsUrl = 'https://quantapi.51ifind.com/api/v1/high_frequency'
    thsPara = {"codes": code,
               "indicators": "close",
               "starttime": start_date,
               "endtime": close_date,
               "functionpara": {
                   "Interval": str(frequency),
                   "Fill": "Original"
               }
               }
    thsResponse = requests.post(url=thsUrl, json=thsPara, headers=thsHeaders)
    data2 = initialization(thsResponse)
    data2 = data2.iloc[:, 1:]
    length0 = len(data2)
    min_length = min(length - i, length0)
    day_limit1 = date_frame.loc[date_frame['fore_code'] == code[:6], 'due_date']
    day_limit = date.fromisoformat(day_limit1.tolist()[0])
    recent_index = i + 1
    if type0 == 0:
        x = 10
    else:
        x = 13
    df.iloc[recent_index - 1, x] = data2.iloc[0, 1]
    for j in range(0, min_length - 1):
        if date.fromisoformat(data2.iloc[j, 0][:10]) >= day_limit:
            break
        min_allowed_count = length - recent_index
        for m in range(0, min_allowed_count):
            if date.fromisoformat(df.iloc[recent_index + m, 2][:10]) >= day_limit:
                break
            if df.iloc[recent_index + m, 2] != data2.iloc[j + 1, 0]:
                # 没找到对应时间
                df.iloc[recent_index + m, x] = data2.iloc[j, 1]
                # 到最后一个时间点
                if recent_index + m == length - 1:
                    break
            else:
                # 找到对应的时间
                df.iloc[recent_index + m, x] = data2.iloc[j + 1, 1]
                recent_index = recent_index + m + 1
                break
    return df


def Data_Calculate(df, frequency, profit_stop, loss_stop, quant0, account, rate, contract_multiplier, date_frame):
    print("计算数据阶段")
    df[['action', 'call_sell_price', 'call_market_price', 'average_call_change', 'put_sell_price', 'put_market_price',
        'average_put_change', 'sum_change', 'account']] = np.zeros((len(df), 9))
    df = df.sort_values(by='time', ascending=True)
    df = df.reset_index()
    df = df.iloc[:, 1:]
    # action在最后的表中是列8
    # action表示的是我每一次操作，count是临时记录用（应对到期的期权），sum_income是总收入
    # number_of_profit_exits是止盈的次数，number_of_stop_losses是止损的次数
    action = 1
    length = len(df)
    sum_income = 0
    number_of_profit_exits = 0
    number_of_stop_losses = 0
    i = 0
    while i < length:
        df.iloc[i, 8] = action
        # 根据action的值来做出相应的判断，第一次进来特殊判断
        if i == 0 or df.iloc[i, 8] != df.iloc[i - 1, 8]:
            # 判断是不是期权存在问题，包括数据的读取问题，假定所有的问题都能通过更换期权合约来解决
            try:
                # 看涨保证金的价格的获取
                index1 = str(df.iloc[i, 2]).find(' ')
                str_date = str(df.iloc[i, 2])[:index1]
                deposit1 = get_the_margin_price(str_date, df.iloc[i, 0])
                # 看跌保证金的价格的获取
                deposit2 = get_the_margin_price(str_date, df.iloc[i, 1])
                # 获取看涨数据
                df = get_the_option_data(df.iloc[i, 0], df.iloc[i, 2], df.iloc[-1, 2], frequency, i, df,
                                         date_frame, 0)
                # 获取看跌数据
                df = get_the_option_data(df.iloc[i, 1], df.iloc[i, 2], df.iloc[-1, 2], frequency, i, df,
                                         date_frame, 1)
                # 形成临时价格
                if i == 0:
                    temp_price = account
                    df.iloc[0, 16] = account
                else:
                    temp_price = df.iloc[i - 1, 16]
                # 计算最多可以卖多少对
                lots = int(temp_price * rate / (deposit1 + deposit2))
            except Exception as e:
                # 这里出错还没有处理，因为假定更换了期权后不再会出错
                print(e)
                sys.exit()
            if lots == 0:
                print("现在的账户总额不足以卖出一对期权合约")
                sys.exit()
            quant = min(quant0, lots)
            # 建立临时list
            arr1 = np.zeros(quant, dtype=float)
            arr2 = np.zeros(quant, dtype=float)
            temp = quant
            df.iloc[i, 9] = df.iloc[i, 10]
            df.iloc[i, 12] = df.iloc[i, 13]
            # 对临时list进行填充
            arr1[0] = df.iloc[i, 10]
            arr2[0] = df.iloc[i, 13]
            temp = temp - 1
        elif df.iloc[i, 8] == df.iloc[i - 1, 8]:
            # 判断当前的日期在当前的期权是不是过期的
            if df.iloc[i, 10] == 0 and df.iloc[i, 13] == 0:
                action = action + 1
                continue
            # 清空表
            df.iloc[i, 0] = np.nan
            df.iloc[i, 1] = np.nan
            df.iloc[i, 4] = np.nan
            df.iloc[i, 5] = np.nan
            df.iloc[i, 6] = np.nan
            df.iloc[i, 7] = np.nan
            # 实现多批的功能
            if temp == 0:
                df.iloc[i, 9] = np.nan
                df.iloc[i, 12] = np.nan
            else:
                df.iloc[i, 9] = df.iloc[i, 10]
                df.iloc[i, 12] = df.iloc[i, 13]
                arr1[quant - temp] = df.iloc[i, 10]
                arr2[quant - temp] = df.iloc[i, 13]
                temp = temp - 1
        # 计算参数，填表
        sum1 = 0
        sum2 = 0
        number = 0
        for j in range(quant):
            if arr1[j] != 0:
                sum1 = sum1 + arr1[j]
                sum2 = sum2 + arr2[j]
                number = number + 1
        average1 = float(sum1) / number
        average2 = float(sum2) / number
        df.iloc[i, 11] = average1 - df.iloc[i, 10]
        df.iloc[i, 14] = average2 - df.iloc[i, 13]
        df.iloc[i, 15] = (df.iloc[i, 11] + df.iloc[i, 14]) * contract_multiplier
        # 判断条件：指的是两个期权卖出去的价差总和除以两个期权卖价总和，如果大于Profit_Taking_Standard或者小于Stop_Loss_Standard
        if df.iloc[i, 15] >= 0:
            df.iloc[i, 16] = temp_price + df.iloc[i, 15] * number
            if (df.iloc[i, 15] * number) / ((sum1 + sum2) * contract_multiplier) >= profit_stop:
                number_of_profit_exits = number_of_profit_exits + 1
                action = action + 1
        else:
            df.iloc[i, 16] = temp_price + df.iloc[i, 15] * number
            if (df.iloc[i, 15] * number) / ((sum1 + sum2) * contract_multiplier) <= loss_stop:
                number_of_stop_losses = number_of_stop_losses + 1
                action = action + 1
        if i == length - 1:
            sum_income = df.iloc[i, 16] - df.iloc[0, 16]
        i = i + 1
    cols_to_delete = ['average_call_change', 'average_put_change']
    df_dropped = df.drop(columns=cols_to_delete, axis=1)
    return sum_income, df_dropped, number_of_profit_exits, number_of_stop_losses


def backtest(code, start_date, end_date, local_path=None, frequency=30, profit_stop=0.1, loss_stop=-0.1, quant=1,
             account=10000, rate=0.5, save_path=None, range_window=300):
    start_time = time.perf_counter()
    # 读取所选期货数据
    data = Import_Futures_Data(code, start_date, end_date, frequency)
    print("导入期货数据阶段")
    # 读取本地的数据
    price_change, key_months, contract_multiplier = Import_Local_Data(code, local_path)
    # 通过期货选择期权
    df, date_frame = Identifying_Options(data, code, price_change=price_change, key_months=key_months,
                                         range_window=range_window, frequency=frequency)
    # 计算整个数据表，得到最终的结果
    df.to_excel('./a.xlsx', index=False)
    date_frame.to_excel('./b.xlsx', index=False)

    income, result, number_of_profit_exits, number_of_stop_losses = Data_Calculate(df, frequency, profit_stop,
                                                                                   loss_stop, quant, account, rate,
                                                                                   contract_multiplier, date_frame)
    # 对结果进行一个总的输出和保存
    result = result.reset_index()
    result = result.iloc[:, 1:]
    annualized_yield = income / account
    percent_string = "{:.2f}%".format(annualized_yield * 100)
    win = number_of_profit_exits / (number_of_profit_exits + number_of_stop_losses)
    strings = ["选择的期货为" + code, "开始时间为" + result.iloc[0, 2] + "  结束时间为" + result.iloc[-1, 2],
               "时间的频率为" + str(frequency) + "分钟",
               "止盈的比例为" + str(profit_stop) + "  止损的比例为" + str(loss_stop),
               "止盈的次数为" + str(number_of_profit_exits) + "次  止损的比例为" + str(number_of_stop_losses) + "次",
               "起始资金为" + str(account) + "元", "风险度为" + str(rate),
               "总收入为" + str(income) + "元", "胜率为" + str(win), "年化收益率为" + percent_string]
    df = pd.DataFrame(strings, columns=['Parameter'])
    if save_path is None:
        save_path = "./期货" + code + "回测数据结果表.xlsx"
    with pd.ExcelWriter(save_path, engine='openpyxl') as writer:
        result.to_excel(writer, sheet_name='sheet1', index=False)
        df.to_excel(writer, sheet_name='Sheet2', index=False)
    print("起始资金为：", account, "账户最后的总额为：", income + account)
    end_time = time.perf_counter()
    total_time = end_time - start_time
    print(f"程序总的运行时间为: {total_time:.6f} 秒")


def main():
    # 输入参数
    #code = input("请输入期货标的（期货活跃连续合约），格式为 【期货代码】 + 【主连】 + 【交易所代码】，比如螺纹钢：RBZL.SHF ")
    #frequency = int(input("请输入行情的频率, 60分钟则输入60 "))
    #start_date = input("请输入起始时间，（格式YYYY-MM-DD hh:mm:ss）")
    #if len(start_date) == 10:
    #    start_date = start_date + " 00:00:00"
    #end_date = input("请输入截至时间，（格式YYYY-MM-DD hh:mm:ss）")
    #if len(end_date) == 10:
    #    end_date = end_date + " 00:00:00"
    #account = int(input("请输入起始资金（正整数）"))
    #risk = float(input("请输入风险度（0 - 1之间的小数）"))
    #profit_stop = float(input("请输入止盈线（0 - 1之间的正小数）"))
    #loss_stop = float(input("请输入止损线（0 - 1之间的负小数）"))
    #quant = int(input("请输入分批处理的批数（正整数），只做一批：1，做多批：>1 "))
    #window = int(input("请输入滑动窗口的大小（大于0的正整数） "))
    #local_path = input(
    #    "请输入读取本地文件的地址，比如 D:\大四下\实习\大地期货\\期权转换表.xlsx,如果没有则默认读取当前目录下面的文件")
    #if local_path == "":
    #    local_path = "./期权转换表.xlsx"
    #save_path = input(
    #    "请输入保存结果文件的地址，比如 D:\大四下\实习\大地期货\测试\回测数据结果表.xlsx,如果没有则默认保存在当前目录下")
    #if save_path == "":
    #    save_path = None
    #backtest(code, start_date, end_date,
    #        local_path=local_path, frequency=frequency, profit_stop=profit_stop, loss_stop=loss_stop,
    #        quant=quant, account=account, rate=risk, save_path=save_path, range_window=window)
    # 对于异常的数据，使用不同的窗口值来测试50为一个间隔(300，250，200，150)
    code = "PGZL.DCE"
    save_path = "D:\大四下\实习\大地期货\测试\期货" + code + "回测数据结果表.xlsx"
    local_path = 'D:\大四下\实习\大地期货\\期权转换表.xlsx'
    backtest(code, "2023-08-05 10:00:00", "2024-07-22 10:00:00",
             local_path=local_path, frequency=30, profit_stop=0.5, loss_stop=-0.5,
             quant=4, account=1000000, rate=0.5, save_path=save_path, range_window=300)


if __name__ == '__main__':
    main()
